import os, sys
import jsonpickle

from osc_base import OSCBaseObject
#from DeviceSystem.DeviceSystem import DeviceSystem
#from DeviceSystem import Groups
import DeviceSystem
from modifiers.EffectsSystem import EffectsSystem
from modifiers.snapshot import Stack, Snapshot

Groups = DeviceSystem.Groups


class MainController(OSCBaseObject):
    _saved_class_name = 'MainController'
    _saved_child_objects = ['Groups', 'CueStacks', 'Submasters']
    #saved_child_classes = ['Stack']
    def __init__(self, **kwargs):
        self.comm = kwargs.get('comm')
        self.parse_args = kwargs.get('parse_args')
        self.osc_address = 'Main'
        self.osc_parent_node = self.comm.osc_io.root_node
        self.osc_child_nodes = {}
        super(MainController, self).__init__(**kwargs)
        self.loading_from_osc = False
        self.build_osc_child_nodes()
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump, 
                                        'dump-response':self.on_osc_dump_response, 
                                        'child-added':self.on_osc_child_added})
                                        
        self.register_signal('new_group', 'new_cuestack', 'new_submaster', 'child_added')
        self.signal_dict_map = {'new_group':'Groups', 'new_cuestack':'CueStacks', 
                                'new_submaster':'Submasters'}
        for sig in self.signal_dict_map.iterkeys():
            self.connect(sig, self.on_signals_to_osc)
            
        ds_kwargs = {'MainController':self}
        ds_kwargs.update(self.add_osc_child(address='Devices'))
        self.DeviceSystem = DeviceSystem.DeviceSystem(**ds_kwargs)
        self.DeviceSystem.connect('new_device', self.on_ds_new_device)
        self.EffectsSystem = EffectsSystem(DeviceSystem=self.DeviceSystem)
        
        self.CueStacks = {}
        self.Submasters = {}
        self.Groups = {}
        
#        for key in ['AllDevices', 'Selection']:
#            gid = '_'.join(['MainController', key])
#            if gid not in self.Groups:
#                self.add_group(id=gid, name=key)
#            setattr(self, key, self.Groups[gid])
#        self.AllDevices.membership_lock = True

        self.load_globals()
        
        f = self.parse_args['options'].filename
        if f:
            self.load_file(filename=f)
            
        #self.comm.connect('state_changed', self.on_comm_state)
        self.comm.osc_io.connect('new_master', self.on_osc_new_master)
        
    def build_osc_child_nodes(self):
        if not hasattr(self, 'osc_child_nodes') or type(self.osc_child_nodes) != dict:
            self.osc_child_nodes = {}
            for name in ['Groups', 'CueStacks', 'Submasters']:
                self.osc_child_nodes.update({name:self.osc_node.add_new_node(name=name)})
                
    def request_osc_updates(self, **kwargs):
        self.osc_node.send_message(address='request-dump', value=list(self.saved_child_objects), to_master=True)
        self.DeviceSystem.request_osc_updates()
            
    def on_osc_new_master(self, **kwargs):
        if not kwargs.get('master_is_local'):
            self.request_osc_updates()
            
    def add_to_selection(self, *args):
        self.Selection.add_devices(args)
        
    def remove_from_selection(self, *args):
        for arg in args:
            self.Selection.del_member(arg.id)
        
    def add_group(self, **kwargs):
        '''
        :Parameters:
            id : 
            name : 
        '''
        kwargs.setdefault('osc_address', kwargs.get('name'))
        kwargs.setdefault('osc_parent_node', self.osc_child_nodes['Groups'])
        group = Groups.DeviceGroup.DeviceGroup(**kwargs)
        group.connect('member_obj_request', self.on_group_member_obj_request)
        self.Groups.update({group.id:group})
        if group.id not in ['_'.join(['MainController', key]) for key in ['AllDevices', 'Selection']]:
            self.emit('new_group', id=group.id, group=group)
            self.emit('child_added')
        return group
    
    def add_CueStack(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
        '''
        kwargs.update({'MainController':self})
        kwargs.setdefault('osc_address', kwargs.get('name'))
        kwargs.setdefault('osc_parent_node', self.osc_child_nodes['CueStacks'])
        stack = Stack(**kwargs)
        self.CueStacks.update({stack.id:stack})
        self.emit('new_cuestack', id=stack.id, stack=stack)
        self.emit('child_added')
        return stack
        
    def store_to_CueStack(self, **kwargs):
        '''
        :Parameters:
            stack : stack object
            stack_id : stack object id
            cue_index : int, cue number, defaults to stack's current cue
            source : str or list, what to store ('all', 'selected', [list, of, objects])
        '''
        stack = kwargs.get('stack')
        stack_id = kwargs.get('stack_id')
        if not stack and stack_id is not None:
            stack = self.CueStacks[stack_id]
        index = kwargs.get('index', stack.current_index)
        source = kwargs.get('source', 'selected')
        if index:
            if type(source) == list or type(source) == tuple:
                obj_list = source
            elif source == 'selected':
                obj_list = self.Selection.members.values()
            elif source == 'all':
                obj_list = self.AllDevices.members.values()
            else:
                obj_list = []
            objects = dict(zip([obj.id for obj in obj_list], obj_list))
            stack.store_snapshot(index=index, objects=objects)
            return True
        return False
    
    def on_ds_new_device(self, **kwargs):
        id = kwargs.get('id')
        if kwargs.get('base') == 'PatchedDevice':
            obj = self.DeviceSystem.PatchedDevices[id]
#            self.AllDevices.membership_lock = False
#            self.AllDevices.add_devices(obj)
#            self.AllDevices.membership_lock = True
            for group in self.Groups.itervalues():
                group.request_member_obj()
            #print 'mc AllDevices:', self.AllDevices.members
            
    def on_group_member_obj_request(self, **kwargs):
        id = kwargs.get('id')
        group = kwargs.get('group')
        d = {}
        m = self.DeviceSystem.PatchedDevices.get(id)
        if not m:
            m = self.Groups.get(id)
        if m:
            d.update({id:m})
            print 'adding:', id, m
        else:
            print 'id not found:', id
            
        group.add_member(**d)
        
    def on_osc_request_dump(self, **kwargs):
        if self.osc_node.oscMaster:
            values = kwargs.get('values')
            client = kwargs.get('client')
            #print 'main...client = ', client
            if len(values):
                for key in values:
                    s = self.to_json(saved_child_objects=[key], indent=None)
                    self.osc_node.send_message(address='dump-response', value=s, client=client)
            else:
                s = self.to_json(indent=None)
                self.osc_node.send_message(address='dump-response', value=s, client=client)
        
    def on_osc_dump_response(self, **kwargs):
        self.loading_from_osc = True
        self.from_json(kwargs.get('values')[0])
        self.loading_from_osc = False
    
    def on_signals_to_osc(self, **kwargs):
        name = kwargs.get('signal_name')
        id = kwargs.get('id')
        key = self.signal_dict_map.get(name)
        if key and not self.loading_from_osc:
            obj = getattr(self, key).get(id)
            if obj:
                s = obj.to_json(indent=None)
                self.osc_node.send_message(address='child-added', value=[key, s])
        
    def on_osc_child_added(self, **kwargs):
        values = kwargs.get('values')
        key = values[0]
        d = jsonpickle.decode(values[1])
        s = jsonpickle.encode({'saved_children':{key:{d['attrs']['id']:d}}})
        self.from_json(s, saved_child_objects=[key])
        
    def load_file(self, **kwargs):
        filename = kwargs.get('filename')
        string = kwargs.get('string', '')
        if filename is not None and os.path.exists(filename):
            file = open(filename, 'r')
            for line in file:
                string += line
            file.close()
        if string != '':
            d = jsonpickle.decode(string)
            for key in ['DeviceSystem', 'EffectsSystem']:
                if key in d:
                    getattr(self, key).from_json(string=jsonpickle.encode(d[key]))
            self.from_json(string)
        self.emit('child_added')
                    
    def save_file(self, **kwargs):
        filename = kwargs.get('filename')
        if filename:
            d = {}
            for key in ['DeviceSystem', 'EffectsSystem']:
                d.update({key:getattr(self, key)._get_saved_attr()})
            file = open(filename, 'w')
            file.write(self.to_json(d, **kwargs))
            file.close()
            self.save_globals()
    
    def load_globals(self, **kwargs):
        kwargs.setdefault('filename', 'Globals.json')
        filename = kwargs.get('filename')
        string = kwargs.get('string', '')
        if filename is not None and os.path.exists(filename):
            file = open(filename, 'r')
            for line in file:
                string += line
            file.close()
        if string != '':
            d = jsonpickle.decode(string)
            for key in ['DeviceSystem', 'EffectsSystem']:
                if key in d:
                    getattr(self, key).load_globals(string=jsonpickle.encode(d[key]))
            self._load_saved_attr(d, saved_child_objects=[])
        
    def save_globals(self, **kwargs):
        filename = kwargs.get('filename', 'Globals.json')
        d = {}
        for key in ['DeviceSystem', 'EffectsSystem']:
            d.update({key:getattr(self, key).save_globals()})
        file = open(filename, 'w')
        file.write(self.to_json(d, saved_child_objects=[]))
        file.close()
    
    def _deserialize_child(self, d):
        self.build_osc_child_nodes()
        if d['saved_class_name'] == 'DeviceGroup' and d['attrs']['id'] not in self.Groups:
            #g_kwargs = self.add_osc_child(address=d['attrs']['name'])
            g_kwargs = dict(deserialize=d, osc_address=d['attrs']['osc_address'], 
                            osc_parent_node=self.osc_child_nodes['Groups'], 
                            signals_to_connect={'member_obj_request':self.on_group_member_obj_request})
            g = Groups.DeviceGroup.DeviceGroup(**g_kwargs)
            #g.connect('member_obj_request', self.on_group_member_obj_request)
            return g
        elif d['saved_class_name'] == 'Stack' and d['attrs']['id'] not in self.CueStacks:
            s_kwargs = dict(MainController=self, deserialize=d, 
                            osc_address=d['attrs']['osc_address'], 
                            osc_parent_node=self.osc_child_nodes['CueStacks'])
            return Stack(**s_kwargs)
        
    def _get_saved_attr(self, **kwargs):
        d = super(MainController, self)._get_saved_attr(**kwargs)
        for key in ['AllDevices', 'Selection']:
            id = '_'.join(['MainController', key])
            if id in d['saved_children'].get('Groups', {}):
                del d['saved_children']['Groups'][id]
        return d
